﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using StyleMVVM.Logging;
using StyleMVVM.ViewModel;
#if NETFX_CORE
using Windows.UI.Xaml;

#else
using System.Windows;
using System.Windows.Controls;
#endif

namespace StyleMVVM.View
{
	public sealed class AttachedEventCommandHelper
	{
		private static readonly string supplemental = typeof(AttachedEventCommandHelper).FullName;
		private static readonly MethodInfo genericConnectMethod;

		private readonly object command;
		private readonly string eventName;

		static AttachedEventCommandHelper()
		{
			genericConnectMethod =
				typeof(AttachedEventCommandHelper).GetTypeInfo().GetDeclaredMethod("GenericConnectHandler");
		}

		public AttachedEventCommandHelper(object command, string eventName)
		{
			this.command = command;
			this.eventName = eventName;
		}

		public object Parameter { get; set; }

		public static AttachedEventCommandHelper GetHelper(DependencyObject obj)
		{
			return (AttachedEventCommandHelper)obj.GetValue(InternalHelperProperty);
		}

		public static void SetHelper(DependencyObject obj, AttachedEventCommandHelper value)
		{
			obj.SetValue(InternalHelperProperty, value);
		}

		public static DependencyProperty HelperProperty
		{
			get { return InternalHelperProperty; }
		}

		// Using a DependencyProperty as the backing store for Helper.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalHelperProperty =
			DependencyProperty.RegisterAttached("Helper",
			                                    typeof(AttachedEventCommandHelper),
			                                    typeof(AttachedEventCommandHelper),
			                                    new PropertyMetadata(null, HelperChanged));

		private static void HelperChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			AttachedEventCommandHelper oldValue = e.OldValue as AttachedEventCommandHelper;
			AttachedEventCommandHelper newValue = e.NewValue as AttachedEventCommandHelper;

			if (oldValue != null)
			{
				oldValue.Disconnect(d);
			}

			if (newValue != null)
			{
				newValue.Connect(d);
			}
		}

		private void Connect(DependencyObject d)
		{
			try
			{
				EventInfo eventInfo = getEventInfo(d.GetType());

				if (eventInfo != null)
				{
					MethodInfo newEventType =
						genericConnectMethod.MakeGenericMethod(eventInfo.EventHandlerType);
					MemberInfo connectMethod = GetType().GetTypeInfo().GetDeclaredMethod("eventHandler");

					newEventType.Invoke(this, new object[] { d, eventInfo, this, connectMethod });
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while connecting event", "StyleMVVM.View", exp);
			}
		}

		internal void GenericConnectHandler<T>(
			DependencyObject d, EventInfo eventInfo, object target, MethodInfo methodInfo) where T : class
		{
			try
			{
				T handlerDelegate = methodInfo.CreateDelegate(eventInfo.EventHandlerType, target) as T;

				Func<T, EventRegistrationToken> add =
					(a) => (EventRegistrationToken)eventInfo.AddMethod.Invoke(d, new object[] { a });

				Action<EventRegistrationToken> remove =
					(a) => eventInfo.RemoveMethod.Invoke(eventInfo, new object[] { a });

				WindowsRuntimeMarshal.AddEventHandler(add, remove, handlerDelegate);
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while setting event handler", typeof(AttachedEventCommandHelper).FullName, exp);
			}
		}

		protected void Disconnect(DependencyObject d)
		{
			try
			{
				EventInfo eventInfo = getEventInfo(d.GetType());

				if (eventInfo != null)
				{
					RoutedEventHandler handlerDelegate =
						GetType().GetTypeInfo().GetDeclaredMethod("eventHandler").
						          CreateDelegate(eventInfo.EventHandlerType, this) as RoutedEventHandler;

					Action<EventRegistrationToken> remove =
						(a) => eventInfo.RemoveMethod.Invoke(eventInfo, new object[] { a });

					WindowsRuntimeMarshal.RemoveEventHandler(remove, handlerDelegate);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while finding event handler", supplemental, exp);
			}
		}

		private EventInfo getEventInfo(Type type)
		{
			EventInfo eventInfo = type.GetRuntimeEvent(eventName);

			if (eventInfo == null)
			{
				Type baseType = type.GetTypeInfo().BaseType;

				if (baseType != null)
				{
					return getEventInfo(type.GetTypeInfo().BaseType);
				}
			}

			return eventInfo;
		}

		private void eventHandler(object sender, object args)
		{
			if (command is ICommand)
			{
				((ICommand)command).Execute(Parameter);
			}
			else if (command is IEventCommand)
			{
				((IEventCommand)command).Execute(sender, args, Parameter);
			}
		}
	}
}